iT邦幫忙

2022 iThome 鐵人賽

DAY 5
0

環境

OS: Windows 10
Editor: Visual Studio Code
Rust version: 1.63.0

如何定義函式

在Rust中,定義函式都已fn作為開頭,下面的範例是一個不回傳任何值不傳入任何參數的函式。

fn hello_function() {
    println!("Hello, function!");
}

參數列的話跟很多語言一樣,都是放在函式名稱後面的括號裡:

fn float_say_hello_to_integar(x: f32, y: i32) {
    println!("Hello, {}! This is {}", y, x);
}

但如果有相同型別的參數的話,Rust不能類似像Golang這樣寫:

fn add_print(x, y: i32) {
    println!("Add: {}", (x + y));
} 
/* 這是golang合法的寫法 */
func AddPrint(x, y int) {
    fmt.Println("Add: ", (x + y))
}

當然不可少的,函式回傳的部分:

fn give_me_five() -> i32 {
    return 5;
}

如果還記得前幾天講到的,回傳多個值時,除了想像C家族的語言那樣,把要回傳的值包成struct,把值包進struct裡回傳回來,或是利用tuple!

fn give_me_555() -> (i32, i32, i32) {
    return (5, 5, 5);
}

fn main() {
    let (a, b, c) = give_me_555(); // 解構
    println!("a is {}", a);
    println!("b is {}", b);
    println!("c is {}", c);
}

再來是一個有趣的點,回傳不一定需要有return,所以下面的寫法是合法的:

fn give_me_num(x: i32) -> i32 {
    x
}

fn main() {
    let x = give_me_num(47);
    println!("x is {}", x);
}

至於為什麼不寫return也可以,下面會試著解說。

陳述句(statement)與表達句(expression)

首先先來澄清甚麼是陳述句(statement)表達句(expression)

這是一個陳述句

let num = 6 + 7;

一個陳述句當中可以包含表達句,以上面的範例6 + 7就是表達句,以我有接觸過的語言來說,雖說一樣有陳述句與表達句的概念,但沒有像Rust那樣需要這麼注意這個概念。

PS: Rust是門基於表達式 (expression-based) 的語言。

從上面的描述的可以知道,表達式會給出一個,再給個範例

let ok = false;

在這裡,false就是表達句。

呼叫函式是表達式,呼叫巨集(macro)是表達式,在scope{}當中進行的也可以是做一個表達式:

// expression
let x = {
    let y = 6;
    y + 1
};

// x is 7

結論,表達句會計算出一個值,並回傳回來,陳述句則不會,陳述句是對某件事的處理的指令

所以這就是為什麼可以直接這樣寫:

// 上面的例子
fn give_me_num(x: i32) -> i32 {
    x
}

如同上面所說的,他計算了一個值x然後把x回傳回去。

由於這個特性的關係,讓我們可以有更簡潔的寫法:

let x = -99;
let sign = if x < 0 { -1 } else { 1 }; 

而且重要的一點,不用去擔心宣告的變數是不是空值(null/nil)

PS: Rust中並沒有null這個概念

// 不安全,可能會有空字串的情況
let num = 5;
let mut name = "";
if num > 3 {
   name = "Tom";
} else {
    name = "Jerry";
}
println!("{}", name);

// 相對安全
let num = 5;
let name = if num > 3 {
    "Tom"
} else {
    "Jerry"
};
println!("{}", name);

main function

我們遇到的第一個函式,應該是main(),許多情況下,main()確實不用回傳任何東西,但有件事引起了我的好奇:

  1. Rust是否能像C和C++標準一樣,回傳類似int作為error code
  2. 如大多靜態語言一樣,如何在開啟程式的時候,傳入參數?

然後確實有這個東西,從官方教材中找到了。

以下是範例,由參數列輸入,嘗試將輸入轉換成數字。

use std::env;
use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
    let args: Vec<String> = env::args().collect();
    let num_str = &args[1];

    let num = match num_str.parse::<i32>() {
        Ok(num) => num,
        Err(e) => return Err(e),
    };
    println!("Num is {}", num);
    Ok(())
}

Wow! 突然多了好多前面沒看過的東西,先一個一個把不清楚的地方標出來:

use std::env;
use std::num::ParseIntError;

// Q1: `Result`看起來是回傳一個泛型?
// Q2: `()` 是什麼型別?
fn main() -> Result<(), ParseIntError> { 
    
    // Q3: `Vec<String>`是Rust的動態陣列嗎?
    // Q4: `collect()`是做什麼用的?
    let args: Vec<String> = env::args().collect();
    
    // Q5: 為什麼要用 &?
    let num_str = &args[1];
    
    // Q6: `parse::<i32>`是Rust泛型函式的用法嗎?
    let num = match num_str.parse::<i32>() {
        Ok(num) => num,
        Err(e) => return Err(e),
    };
    println!("Num is {}", num);
    Ok(())
}

接下來一個一個開始去找答案,當然可能會發現找A的時候,會出現B、C、D等新名詞,甚至是完全理解錯誤,但就先稍微了解一下,等之後再碰到,再來填坑 (挖坑~ 挖坑中~)。

Q1 甚麼是Result

這是一個generics enum的型別,實際定義長這樣:

enum Result<T, E> { // T and E are generics. T can contain any type of value, E can be any error.
    Ok(T),
    Err(E),
}

這屬於Rust錯誤處理的方式,其他語言中都有像是nullnil來表示空值,但Rust中並沒有,而是用Option<T>回傳Some(value)表示某值或是None表示空值,Result<T, E>則是會回傳錯誤,如果錯誤發生的話。

Q2 () Type

這個可以視為C語言家族中的void

以上面的範例來看的話,就會是main function回傳一個Result<(), ParseIntError>,如果錯誤的話,就回傳錯誤(Line: 19),正常就表示不想要任何回傳值(Line: 22)。

Q3 Vec<>是Rust的動態陣列嗎?

確實沒錯!以範例來看就是Rust中的動態陣列(Growable array),類似C++的std::vector
參考文件式這份

Q4 collect()是做什麼用的?

這個問題要看整個表達式(expression),env::args().collect(),前面env::args()挺好理解的,就是要取得程式開啟時傳入的參數列,但查詢文件可以得知,env::args()回傳的是迭代器(iterator),後面的collect()則是把迭代器轉換成集合(collection)。

文件請見這裡,進去之後還要搜尋一下collect,就會找到了。

Q5 為什麼要用&?

問這個問題其實還有另外一個點,雖然八九不離十跟參考(Reference)之類的有關係,但是為什麼一定要拿它的參考,於是我把&拿掉看看:

let args: Vec<String> = env::args().collect();
let num_str = args[1]; // 把`&`拿掉看看

compiler回這個錯誤給我:

error[E0507]: cannot move out of index of `Vec<String>`
 --> src\main.rs:6:19
  |
6 |     let num_str = args[1];
  |                   ^^^^^^^
  |                   |
  |                   move occurs because value has type `String`, which does not implement the `Copy` trait
  |                   help: consider borrowing here: `&args[1]`

For more information about this error, try `rustc --explain E0507`.

查了一下,看起來跟是String這個型別不給複製,因為沒有相關對於Copy的實作。

Q6 parse::<i32>是Rust泛型函式的用法嗎?

這個確實為Rust泛型函式的用法,參考的地方在這裡,但目前只想先知道式泛型就好了,等到介紹到他之後,再去進一步了解它。

後續

本篇的後面,稍微找了一下關於我對main function的疑惑,當然是很草率的了解一下,尤其是關於參考與複製的部分,由於碰過C和C++,有把這兩種語言的特性帶進去思考,但跟Rust比起來有很多地方都不一樣......

Reference


上一篇
[Rust] 控制流程 (Control flow)
下一篇
[Rust] 記憶體管理
系列文
嘗試30天學「不」會Rust18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言